//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.IO;
using EffiProz.Core.Lib;
using EffiProz.Core.Errors;
using EffiProz.Core.Lib.Tar;


namespace EffiProz.Core.Persist
{
    public class Logger
    {
        public SimpleLog appLog;
        public Log log;
        private Database database;
        private LockFile lockFile;
        public bool checkpointRequired;
        public bool checkpointDue;
        public bool checkpointDisabled;
        private bool logsStatements;
        private bool loggingEnabled;
        private bool syncFile = false;

        //
        public bool propFilesReadOnly;
        public bool propDatabaseReadOnly;
        public bool propIncrementBackup;
        public bool propNioDataFile;
        public int propMaxFreeBlocks;
        public int propCacheMaxRows;
        public int propCacheMaxSize;
        public int propCacheFileScale;
        public int propCacheDefragLimit;
        String propTextSourceDefault = "";
        EffiProzProperties propTextSourceProps;
        bool propTextAllowFullPath;
        public int propWriteDelay;
        public int propLogSize;
        int propEventLogLevel;
        int propGC;
        int propTxMode = Database.LOCKS;

        //
        private Crypto crypto;
        public EfzFileAccess fileAccess;
        public bool _isStoredFileAccess;
        String tempDirectoryPath;

        //
        public bool isNewDatabase;

        public Logger(Database database)
        {
            this.database = database;
        }

        /**
         *  Opens the specified Database object's database files and starts up
         *  the logging process. <p>
         *
         *  If the specified Database object is a new database, its database
         *  files are first created.
         *
         * @param  db the Database
         * @throws  CoreException if there is a problem, such as the case when
         *      the specified files are in use by another process
         */
        public void openPersistence()
        {


            fileAccess = new FileUtil();


            bool isFile =
                DatabaseURL.isFileBasedDatabaseType(database.getType());

            database.databaseProperties = new EffiProzDatabaseProperties(database);
            isNewDatabase = !isFile
                            || !fileAccess.IsStreamElement(database.getPath()
                                + ".script");

            if (isNewDatabase)
            {
                String name = newUniqueName();

                database.setUniqueName(name);

                bool checkExists = database.urlProperties.isPropertyTrue(
                                          EffiProzDatabaseProperties.url_ifexists);

                if (checkExists)
                {
                    throw Error.error(ErrorCode.DATABASE_NOT_EXISTS,
                                      database.getPath());
                }

                database.databaseProperties.setURLProperties(
                    database.urlProperties);
            }
            else
            {
                database.databaseProperties.load();

                if (database.urlProperties.isPropertyTrue(
                        EffiProzDatabaseProperties.effiproz_files_readonly))
                {
                    database.databaseProperties.setProperty(
                        EffiProzDatabaseProperties.effiproz_files_readonly, true);
                }

                if (database.urlProperties.isPropertyTrue(
                        EffiProzDatabaseProperties.effiproz_readonly))
                {
                    database.databaseProperties.setProperty(
                        EffiProzDatabaseProperties.effiproz_readonly, true);
                }
            }

            setVariables();

            String logPath = null;

            if (DatabaseURL.isFileBasedDatabaseType(database.getType())
                    && !database.isFilesReadOnly())
            {
                logPath = database.getPath() + ".app.log";
            }

            this.appLog = new SimpleLog(logPath, propEventLogLevel);

            if (!isFile)
            {
                return;
            }

            checkpointRequired = false;
            loggingEnabled = false;

            bool useLock = database.getProperties().isPropertyTrue(
                EffiProzDatabaseProperties.effiproz_lock_file);

            if (useLock && !database.isFilesReadOnly())
            {
                acquireLock(database.getPath());
            }

            log = new Log(database);

            log.open();

            logsStatements = loggingEnabled = !database.isFilesReadOnly();

            String version = database.databaseProperties.getProperty(
                EffiProzDatabaseProperties.effiproz_version);


            if (database.getUniqueName() == null)
            {
                database.setUniqueName(newUniqueName());
            }
        }

        public void setVariables()
        {

            String cryptKey = database.urlProperties.getProperty(
                EffiProzDatabaseProperties.url_crypt_key);

            if (cryptKey != null)
            {
                String cryptType = database.urlProperties.getProperty(
                    EffiProzDatabaseProperties.url_crypt_type);
                String cryptIv = database.urlProperties.getProperty(
                    EffiProzDatabaseProperties.url_crypt_iv);

                crypto = new Crypto(cryptKey,cryptIv, cryptType);
            }

            if (database.databaseProperties.isPropertyTrue(
                    EffiProzDatabaseProperties.effiproz_readonly))
            {
                database.setReadOnly();
            }

            if (database.databaseProperties.isPropertyTrue(
                    EffiProzDatabaseProperties.effiproz_files_readonly))
            {
                database.setFilesReadOnly();
            }

            // handle invalid paths as well as access issues
            if (!database.isFilesReadOnly())
            {
                if (database.getType() == DatabaseURL.S_MEM)
                {
                    tempDirectoryPath = database.getProperties().getStringProperty(
                        EffiProzDatabaseProperties.effiproz_temp_directory);
                }
                else
                {
                    tempDirectoryPath = database.getPath() + ".tmp";
                }

                if (tempDirectoryPath != null)
                {
                    tempDirectoryPath =
                        FileUtil.makeDirectories(tempDirectoryPath);
                }
            }

            if (tempDirectoryPath != null)
            {
                int rows = database.databaseProperties.getIntegerProperty(
                    EffiProzDatabaseProperties.effiproz_result_max_memory_rows);

                database.setResultMaxMemoryRows(rows);
            }

            String tableType = database.databaseProperties.getStringProperty(
                EffiProzDatabaseProperties.effiproz_default_table_type);

            if ("CACHED".Equals(tableType, StringComparison.OrdinalIgnoreCase))
            {
                database.schemaManager.setDefaultTableType(TableBase.CACHED_TABLE);
            }

            String txMode = database.databaseProperties.getStringProperty(
                EffiProzDatabaseProperties.effiproz_tx);

            if (Tokens.T_MVCC.Equals(txMode, StringComparison.OrdinalIgnoreCase))
            {
                propTxMode = Database.MVCC;
            }
            else if (Tokens.T_MVLOCKS.Equals(txMode, StringComparison.OrdinalIgnoreCase))
            {
                propTxMode = Database.MVLOCKS;
            }
            else if (Tokens.T_LOCKS.Equals(txMode, StringComparison.OrdinalIgnoreCase))
            {
                propTxMode = Database.LOCKS;
            }

            switch (propTxMode)
            {

                case Database.LOCKS:
                    database.txManager = new TransactionManager2PL(database);
                    break;

                case Database.MVLOCKS:
                    database.txManager = new TransactionManagerMV2PL(database);
                    break;

                case Database.MVCC:
                    database.txManager = new TransactionManagerMVCC(database);
                    break;
            }

            database.sqlEnforceSize = database.databaseProperties.isPropertyTrue(
                EffiProzDatabaseProperties.sql_enforce_strict_size);
            database.sqlEnforceSize = database.databaseProperties.isPropertyTrue(
                EffiProzDatabaseProperties.sql_enforce_size);
            database.sqlEnforceNames = database.databaseProperties.isPropertyTrue(
                EffiProzDatabaseProperties.sql_enforce_names);

            if (database.databaseProperties.isPropertyTrue(
                    EffiProzDatabaseProperties.sql_compare_in_locale))
            {
                database.collation.setCollationAsLocale();
            }

            propEventLogLevel = database.databaseProperties.getIntegerProperty(
                EffiProzDatabaseProperties.effiproz_applog);
            propFilesReadOnly = database.databaseProperties.isPropertyTrue(
                EffiProzDatabaseProperties.effiproz_files_readonly);
            propDatabaseReadOnly = database.databaseProperties.isPropertyTrue(
                EffiProzDatabaseProperties.effiproz_readonly);
            propIncrementBackup = database.databaseProperties.isPropertyTrue(
                EffiProzDatabaseProperties.effiproz_inc_backup);
            propNioDataFile = database.databaseProperties.isPropertyTrue(
                EffiProzDatabaseProperties.effiproz_nio_data_file);
            propCacheMaxRows = database.databaseProperties.getIntegerProperty(
                EffiProzDatabaseProperties.effiproz_cache_rows);
            propCacheMaxSize =
                database.databaseProperties.getIntegerProperty(
                    EffiProzDatabaseProperties.effiproz_cache_size) * 1024;
            propCacheFileScale = database.databaseProperties.getIntegerProperty(
                EffiProzDatabaseProperties.effiproz_cache_file_scale);
            propCacheDefragLimit = database.databaseProperties.getIntegerProperty(
                EffiProzDatabaseProperties.effiproz_defrag_limit);
            propMaxFreeBlocks = database.databaseProperties.getIntegerProperty(
                EffiProzDatabaseProperties.effiproz_cache_free_count_scale);
            propMaxFreeBlocks = 1 << propMaxFreeBlocks;
            propTextAllowFullPath = database.databaseProperties.isPropertyTrue(
                EffiProzDatabaseProperties.textdb_allow_full_path);
            propWriteDelay =
                database.databaseProperties.isPropertyTrue(
                    EffiProzDatabaseProperties.effiproz_write_delay) ? 10000
                                                               : 0;
            propLogSize = database.databaseProperties.getIntegerProperty(
                EffiProzDatabaseProperties.effiproz_log_size);
            propGC = database.databaseProperties.getIntegerProperty(
                EffiProzDatabaseProperties.runtime_gc_interval);

            database.setMetaDirty(false);
        }

        // fredt@users 20020130 - patch 495484 by boucherb@users

        /**
         *  Shuts down the logging process using the specified mode. <p>
         *
         * @param  closemode The mode in which to shut down the logging
         *      process
         *      <OL>
         *        <LI> closemode -1 performs SHUTDOWN IMMEDIATELY, equivalent
         *        to  a poweroff or crash.
         *        <LI> closemode 0 performs a normal SHUTDOWN that
         *        checkpoints the database normally.
         *        <LI> closemode 1 performs a shutdown compact that scripts
         *        out the contents of any CACHED tables to the log then
         *        deletes the existing *.data file that contains the data
         *        for all CACHED table before the normal checkpoint process
         *        which in turn creates a new, compact *.data file.
         *        <LI> closemode 2 performs a SHUTDOWN SCRIPT.
         *      </OL>
         *
         * @return  true if closed with no problems or false if a problem was
         *        encountered.
         */
        public bool closePersistence(int closemode)
        {

            if (log == null)
            {
                return true;
            }

            try
            {
                switch (closemode)
                {

                    case Database.CLOSEMODE_IMMEDIATELY:
                        log.shutdown();
                        break;

                    case Database.CLOSEMODE_NORMAL:
                        log.close(false);
                        break;

                    case Database.CLOSEMODE_COMPACT:
                    case Database.CLOSEMODE_SCRIPT:
                        log.close(true);
                        break;
                }
            }
            catch (Exception e)
            {
                logSevereEvent("error closing log", e);

                log = null;

                return false;
            }

            logInfoEvent("Database closed");

            log = null;

            appLog.close();

            return true;
        }

        String newUniqueName()
        {

            String name = StringUtil.toPaddedString(String.Format("{0:x}", DateTime.Now.Ticks)
                , 16, '0', false);

            name = "EffiProz" + name.Substring(6).ToUpperInvariant();

            return name;
        }

        /**
         *  Determines if the logging process actually does anything. <p>
         *
         *  In-memory Database objects do not need to log anything. This
         *  method is essentially equivalent to testing whether this logger's
         *  database is an in-memory mode database.
         *
         * @return  true if this object encapsulates a non-null Log instance,
         *      else false
         */
        public bool hasPersistence()
        {
            return log != null;
        }



        public void setEventLogLevel(int level)
        {

            propEventLogLevel = level;

            appLog.setLevel(level);
        }

        public void logSevereEvent(String message, Exception t)
        {


            if (appLog != null)
            {
                appLog.logContext(t, message);
            }
        }

        public void logWarningEvent(String message, Exception t)
        {


            appLog.logContext(t, message);
        }

        public void logInfoEvent(String message)
        {

            appLog.logContext(SimpleLog.LOG_NORMAL, message);
        }

        /**
         *  Returns the Cache object or null if one doesn't exist.
         */
        public DataFileCache getCache()
        {

            if (log == null)
            {
                return null;
            }
            else
            {
                return log.getCache();
            }
        }

        /**
         *  Returns true if Cache object exists.
         */
        public bool hasCache()
        {

            if (log == null)
            {
                return false;
            }
            else
            {
                return log.hasCache();
            }
        }

        /**
         *  Records a Log entry representing a new connection action on the
         *  specified Session object.
         *
         * @param  session the Session object for which to record the log
         *      entry
         * @throws  CoreException if there is a problem recording the Log
         *      entry
         */
        public void logStartSession(Session session)
        {
            lock (this)
            {
                if (loggingEnabled)
                {
                    writeToLog(session, session.getUser().getConnectUserSQL());
                }
            }
        }

        /**
         *  Records a Log entry for the specified SQL statement, on behalf of
         *  the specified Session object.
         *
         * @param  session the Session object for which to record the Log
         *      entry
         * @param  statement the SQL statement to Log
         * @throws  CoreException if there is a problem recording the entry
         */
        public void writeToLog(Session session, String statement)
        {
            lock (this)
            {
                if (loggingEnabled && log != null)
                {
                    log.writeStatement(session, statement);
                }
            }
        }

        public void writeInsertStatement(Session session,
                Table table, Object[] row)
        {
            lock (this)
            {
                if (loggingEnabled)
                {
                    log.writeInsertStatement(session, table, row);
                }
            }
        }

        public void writeDeleteStatement(Session session, Table t,
                Object[] row)
        {
            lock (this)
            {
                if (loggingEnabled)
                {
                    log.writeDeleteStatement(session, t, row);
                }
            }
        }

        public void writeSequenceStatement(Session session,
                NumberSequence s)
        {
            lock (this)
            {
                if (loggingEnabled)
                {
                    log.writeSequenceStatement(session, s);
                }
            }
        }

        public void writeCommitStatement(Session session)
        {
            lock (this)
            {
                if (loggingEnabled)
                {
                    log.writeCommitStatement(session);
                    synchLog();
                }
            }
        }

        /**
         * Called after commits or after each statement when autocommit is on
         */
        public void synchLog()
        {
            lock (this)
            {
                if (loggingEnabled && syncFile)
                {
                    log.synchLog();
                }
            }
        }

        public void synchLogForce()
        {
            lock (this)
            {
                if (loggingEnabled)
                {
                    log.synchLog();
                }
            }
        }

        /**
         *  Checkpoints the database. <p>
         *
         *  The most important effect of calling this method is to cause the
         *  log file to be rewritten in the most efficient form to
         *  reflect the current state of the database, i.e. only the DDL and
         *  insert DML required to recreate the database in its present state.
         *  Other house-keeping duties are performed w.r.t. other database
         *  files, in order to ensure as much as possible the ACID properites
         *  of the database.
         *
         * @throws  CoreException if there is a problem checkpointing the
         *      database
         */
        public void checkpoint(bool mode)
        {
            lock (this)
            {
                if (loggingEnabled)
                {
                    logInfoEvent("Checkpoint start");
                    log.checkpoint(mode);
                    database.sessionManager.resetLoggedSchemas();
                    logInfoEvent("Checkpoint end");
                }

                checkpointDue = false;
            }
        }

        /**
         *  Sets the maximum size to which the log file can grow
         *  before being automatically checkpointed.
         *
         * @param  megas size in MB
         */
        public void setLogSize(int megas)
        {
            lock (this)
            {
                propLogSize = megas;

                if (log != null)
                {
                    log.setLogSize(propLogSize);
                }
            }
        }

        /**
         *  Sets the type of script file, currently 0 for text (default)
         *  1 for binary and 3 for compressed
         *
         * @param  i The type
         */
        public void setScriptType(int i)
        {
            lock (this)
            {
                if (log != null)
                {

                    //
                }
            }
        }

        /**
         *  Sets the log write delay mode to number of seconds. By default
         *  executed commands written to the log are committed fully at most
         *  60 second after they are executed. This improves performance for
         *  applications that execute a large number
         *  of short running statements in a short period of time, but risks
         *  failing to log some possibly large number of statements in the
         *  event of a crash. A small value improves recovery.
         *  A value of 0 will severly slow down logging when autocommit is on,
         *  or many short transactions are committed.
         *
         * @param  delay in seconds
         */
        public void setWriteDelay(int delay)
        {
            lock (this)
            {
                propWriteDelay = delay;

                if (log != null)
                {
                    syncFile = (delay == 0);

                    log.setWriteDelay(delay);
                }
            }
        }

        public Crypto getCrypto()
        {
            return crypto;
        }

        public int getWriteDelay()
        {
            return propWriteDelay;
        }

        public int getLogSize()
        {
            return propLogSize;
        }

        public int getScriptType()
        {
            return log != null ? log.getScriptType()
                               : 0;
        }

        public void setIncrementBackup(bool val)
        {
            lock (this)
            {
                if (val == propIncrementBackup)
                {
                    return;
                }

                propIncrementBackup = val;

                if (log != null)
                {
                    log.setIncrementBackup(val);

                    if (log.hasCache())
                    {
                        database.logger.checkpointRequired = true;
                    }
                }
            }
        }

        public void setCacheMaxRows(int value)
        {
            propCacheMaxRows = value;
        }

        public int getCacheRowsDefault()
        {
            return propCacheMaxRows;
        }

        public void setCacheSize(int value)
        {
            propCacheMaxSize = value * 1024;
        }

        public int getCacheSize()
        {
            return propCacheMaxSize;
        }

        public void setCacheFileScale(int value)
        {
            propCacheFileScale = value;
        }

        public int getCacheFileScale()
        {
            return propCacheFileScale;
        }

        public void setDefagLimit(int value)
        {
            propCacheDefragLimit = value;
        }

        public int getDefragLimit()
        {
            return propCacheDefragLimit;
        }

        public void setDefaultTextTableProperties(String source,
                EffiProzProperties props)
        {
            this.propTextSourceDefault = source;
            this.propTextSourceProps = props;
        }

        /**
         *  Opens the TextCache object.
         */
        public DataFileCache openTextFilePersistence(Table table, String source,
                bool readOnlyData, bool reversed)
        {
            return log.openTextCache(table, source, readOnlyData, reversed);
        }

        /**
         *  Closes the TextCache object.
         */
        public void closeTextCache(Table table)
        {
            log.closeTextCache(table);
        }

        public bool needsCheckpointReset()
        {
            lock (this)
            {
                if (checkpointRequired && !checkpointDue && !checkpointDisabled)
                {
                    checkpointDue = true;
                    checkpointRequired = false;

                    return true;
                }

                checkpointRequired = false;

                return false;
            }
        }

        public void stopLogging()
        {
            loggingEnabled = false;
        }

        public void restartLogging()
        {
            loggingEnabled = logsStatements;
        }

        public bool hasLockFile()
        {
            return lockFile != null;
        }

        public void acquireLock(String path)
        {

            if (lockFile != null)
            {
                return;
            }

            lockFile = LockFile.newLockFileLock(path);
        }

        public void releaseLock()
        {

            try
            {
                if (lockFile != null)
                {
                    lockFile.tryRelease();
                }
            }
            catch (Exception ) { }

            lockFile = null;
        }

        // properties
        public void setNioDataFile(bool value)
        {
            propNioDataFile = value;
        }

        public EfzFileAccess getFileAccess()
        {
            return fileAccess;
        }

        public bool isStoredFileAccess()
        {
            return _isStoredFileAccess;
        }

        public String getTempDirectoryPath()
        {
            return tempDirectoryPath;
        }

        public PersistentStore newStore(Session session,
                                        PersistentStoreCollection collection,
                                        TableBase table, bool diskBased)
        {

            switch (table.getTableType())
            {

                case TableBase.CACHED_TABLE:
                    DataFileCache cache = getCache();

                    if (cache == null)
                    {
                        break;
                    }

                    return new RowStoreAVLDisk(collection, cache, (Table)table);

                case TableBase.MEMORY_TABLE:
                case TableBase.SYSTEM_TABLE:
                    return new RowStoreAVLMemory(collection, (Table)table);

                case TableBase.TEXT_TABLE:
                    return new RowStoreAVLDiskData(collection, (Table)table);

                case TableBase.TEMP_TABLE:
                    diskBased = false;
                    goto case TableBase.RESULT_TABLE;
                // fall through
                case TableBase.RESULT_TABLE:
                case TableBase.SYSTEM_SUBQUERY:
                case TableBase.VIEW_TABLE:
                case TableBase.TRANSITION_TABLE:
                    if (session == null)
                    {
                        return null;
                    }

                    switch (table.persistenceScope)
                    {

                        case TableBase.SCOPE_STATEMENT:
                            return new RowStoreAVLHybrid(session, collection,
                                                         table, diskBased);

                        case TableBase.SCOPE_TRANSACTION:
                            return new RowStoreAVLHybrid(session, collection,
                                                         table, diskBased);

                        case TableBase.SCOPE_SESSION:
                            return new RowStoreAVLHybrid(session, collection,
                                                         table, diskBased);
                    }
                    break;
            }

            throw Error.runtimeError(ErrorCode.U_S0500, "Logger");
        }

        public String[] getPropertiesSQL()
        {

            EfzArrayList list = new EfzArrayList();
            StringBuilder sb = new StringBuilder();

            sb.Append("SET DATABASE ").Append(Tokens.T_UNIQUE).Append(' ');
            sb.Append(Tokens.T_NAME).Append(' ').Append(database.getUniqueName());
            list.add(sb.ToString());
            sb.Length = (0);
            sb.Append("SET DATABASE ").Append(Tokens.T_GC).Append(' ');
            sb.Append(propGC);
            list.add(sb.ToString());
            sb.Length = (0);
            sb.Append("SET DATABASE ").Append(Tokens.T_DEFAULT).Append(' ');
            sb.Append(Tokens.T_RESULT).Append(' ').Append(Tokens.T_MEMORY);
            sb.Append(' ').Append(Tokens.T_ROWS).Append(' ');
            sb.Append(database.getResultMaxMemoryRows());
            list.add(sb.ToString());
            sb.Length = (0);
            sb.Append("SET DATABASE ").Append(Tokens.T_EVENT).Append(' ');
            sb.Append(Tokens.T_LOG).Append(' ').Append(Tokens.T_LEVEL);
            sb.Append(' ').Append(propEventLogLevel);
            list.add(sb.ToString());
            sb.Length = (0);
            sb.Append("SET DATABASE ").Append(Tokens.T_SQL).Append(' ');
            sb.Append(Tokens.T_SIZE).Append(' ');
            sb.Append(database.sqlEnforceSize ? Tokens.T_TRUE
                                              : Tokens.T_FALSE);
            list.add(sb.ToString());
            sb.Length = (0);
            sb.Append("SET DATABASE ").Append(Tokens.T_SQL).Append(' ');
            sb.Append(Tokens.T_NAMES).Append(' ');
            sb.Append(database.sqlEnforceNames ? Tokens.T_TRUE
                                               : Tokens.T_FALSE);
            list.add(sb.ToString());
            sb.Length = (0);
            sb.Append("SET DATABASE ").Append(Tokens.T_TRANSACTION);
            sb.Append(' ').Append(Tokens.T_CONTROL).Append(' ');

            switch (database.txManager.getTransactionControl())
            {

                case Database.MVCC:
                    sb.Append(Tokens.T_MVCC);
                    break;

                case Database.MVLOCKS:
                    sb.Append(Tokens.T_MVLOCKS);
                    break;

                case Database.LOCKS:
                    sb.Append(Tokens.T_LOCKS);
                    break;
            }

            list.add(sb.ToString());
            sb.Length = (0);

            if (hasPersistence())
            {
                if (database.schemaManager.getDefaultTableType()
                        == TableBase.CACHED_TABLE)
                {
                    list.add("SET DATABASE DEFAULT TABLE TYPE CACHED");
                }

                int delay = propWriteDelay;
                bool millis = delay > 0 && delay < 1000;

                if (millis)
                {
                    if (delay < 20)
                    {
                        delay = 20;
                    }
                }
                else
                {
                    delay /= 1000;
                }

                sb.Length = (0);
                sb.Append("SET FILES ").Append(Tokens.T_WRITE).Append(' ');
                sb.Append(Tokens.T_DELAY).Append(' ').Append(delay);

                if (millis)
                {
                    sb.Append(' ').Append(Tokens.T_MILLIS);
                }

                list.add(sb.ToString());
                sb.Length = (0);
                sb.Append("SET FILES ").Append(Tokens.T_BACKUP);
                sb.Append(' ').Append(Tokens.T_INCREMENT).Append(' ');
                sb.Append(propIncrementBackup ? Tokens.T_TRUE
                                              : Tokens.T_FALSE);
                list.add(sb.ToString());
                sb.Length = (0);
                sb.Append("SET FILES ").Append(Tokens.T_CACHE);
                sb.Append(' ').Append(Tokens.T_SIZE).Append(' ');
                sb.Append(propCacheMaxSize / 1024);
                list.add(sb.ToString());
                sb.Length = (0);
                sb.Append("SET FILES ").Append(Tokens.T_CACHE);
                sb.Append(' ').Append(Tokens.T_ROWS).Append(' ');
                sb.Append(propCacheMaxRows);
                list.add(sb.ToString());
                sb.Length = (0);
                sb.Append("SET FILES ").Append(Tokens.T_SCALE);
                sb.Append(' ').Append(propCacheFileScale);
                list.add(sb.ToString());
                sb.Length = (0);
                sb.Append("SET FILES ").Append(Tokens.T_DEFRAG);
                sb.Append(' ').Append(propCacheDefragLimit);
                list.add(sb.ToString());
                sb.Length = (0);
                sb.Append("SET FILES ").Append(Tokens.T_NIO);
                sb.Append(' ').Append(Tokens.T_TRUE);
                list.add(sb.ToString());
                sb.Length = (0);
                sb.Append("SET FILES ").Append(Tokens.T_LOG).Append(' ');
                sb.Append(Tokens.T_SIZE).Append(' ').Append(propLogSize);
                list.add(sb.ToString());
                sb.Length = (0);
                /*
                if (propTempDirectoryPath != null) {
                    sb.Append("SET FILES ").Append(Tokens.T_TEMP).Append(' ');
                    sb.Append(Tokens.T_PATH).Append(' ');
                    sb.Append(propTempDirectoryPath);
                    list.add(sb.ToString());
                    sb.setLength(0);
                }
                */
                sb.Append("SET DATABASE ").Append(Tokens.T_TEXT).Append(' ');
                sb.Append(Tokens.T_TABLE).Append(' ').Append(Tokens.T_DEFAULTS);
                sb.Append(' ').Append('\'');
                sb.Append(propTextSourceDefault).Append('\'');
                list.add(sb.ToString());
                sb.Length = (0);
            }

            String[] array = new String[list.size()];

            list.toArray(array);

            return array;
        }

        //
        static private string backupFileFormat =
            "yyyyMMdd'T'HHmmss";
        static private Char runtimeFileDelim = '\n';

        public void backup(String destPath, String dbPath,
                                        bool script, bool blocking,
                                        bool compressed)
        {
          
            lock (this)
            {
                /* If want to add db Id also, will need to pass either Database
                 * instead of dbPath, or pass dbPath + Id from CommandStatement.
                 */


                String instanceName = new FileInfo(dbPath).FullName;

                if (destPath == null || destPath.Length < 1)
                {
                    throw Error.error(ErrorCode.X_2200F, "0-length destination path");
                }

                char lastChar = destPath[destPath.Length - 1];
                bool generateName = (lastChar == '/'
                                        || lastChar == runtimeFileDelim);
                String defaultCompressionSuffix = compressed ? ".tar.gz"
                                                             : ".tar";
                FileInfo archiveFile =
                    generateName
                    ? (new FileInfo(instanceName + '-'
                                + DateTime.Now.ToString(backupFileFormat)
                                + defaultCompressionSuffix))
                    : (new FileInfo(destPath));
                bool nameImpliesCompress =
                    archiveFile.Name.EndsWith(".tar.gz")
                    || archiveFile.Name.EndsWith(".tgz");

                if ((!nameImpliesCompress)
                        && !archiveFile.Name.EndsWith(".tar"))
                {
                    throw Error.error(null, ErrorCode.UNSUPPORTED_FILENAME_SUFFIX, 0,
                                      new String[] {
                archiveFile.Name, ".tar, .tar.gz, .tgz"
            });
                }

                if (compressed != nameImpliesCompress)
                {
                    throw Error.error(null, ErrorCode.COMPRESSION_SUFFIX_MISMATCH, 0,
                                      new Object[] {
                (compressed), archiveFile.Name
            });
                }

                log.checkpointClose();

                try
                {
                    logInfoEvent("Initiating backup of instance '" + instanceName
                                 + "'");

                    // By default, DbBackup will throw if archiveFile (or
                    // corresponding work file) already exist.  That's just what we
                    // want here.
                    DbBackup backup = new DbBackup(archiveFile, dbPath);

                    backup.setAbortUponModify(false);
                    backup.write();
                    logInfoEvent("Successfully backed up instance '" + instanceName
                                 + "' to '" + destPath + "'");

                    // RENAME tempPath to destPath
                }
                catch (ArgumentException iae)
                {
                    throw Error.error(ErrorCode.X_HV00A, iae.Message);
                }
                catch (IOException ioe)
                {
                    throw Error.error(ErrorCode.FILE_IO_ERROR, ioe.Message);
                }
                catch (TarException tme)
                {
                    throw Error.error(ErrorCode.FILE_IO_ERROR, tme.Message);
                }
                finally
                {
                    log.checkpointReopen();
                }
            }
        }
    }

}
